package client;

import java.awt.Cursor;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.concurrent.Semaphore;

import javax.swing.JOptionPane;

import utils.ITableConnection;
import utils.Piece;
import utils.Settings;

import commands.CommandGameResultToOwner;
import commands.CommandMove;
import commands.CommandWeaponSelection;
import components.RPSGlassPane;

import enums.Direction;
import enums.PlayerColor;
import enums.Weapon;

/**
 * The controller in the MVC design pattern. Represents the logics of the game itself.
 */
public class Controller implements MouseListener, MouseMotionListener, KeyListener {

	private Model model = null;
	private Board board = null;
	private ITableConnection conn = null;
	private TableFrame tableFrame = null;
	private Point currentlyPressedPoint = null;
	
	public Point from = null;
	public Point to = null;
	

	public Controller (Model model, Board board) {
		this.model = model;
		this.board = board;
	}

	public void setConnection (ITableConnection conn) {
		this.conn = conn;
	}

	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	public void mouseClicked(MouseEvent e) {}
	public void mouseReleased (MouseEvent e) {}
	
	public void mouseDragged (MouseEvent e) {}
	
	public void keyPressed(KeyEvent e) {}
	public void keyReleased(KeyEvent e) {}
	public void keyTyped(KeyEvent e) {}

	public void mouseMoved(MouseEvent e) {

		int mouseX = e.getX();
		int mouseY = e.getY();
		
		if (model.getGameState() == Model.GAME_STATE_PLACING_FLAG || model.getGameState() == Model.GAME_STATE_PLACING_TRAP) {
			model.setMousePosition(mouseX, mouseY);
			this.model.refreshObservers();
		}
	}

	public void mousePressed (MouseEvent e) {
	
		int mouseX = e.getX();
		int mouseY = e.getY();
		
		if (model.getGameState() == Model.GAME_STATE_PLACING_FLAG && model.isPlayerViewer() == false) {

			mouseX = e.getX();
			mouseY = e.getY();
			Point point = board.getBoardCoords(mouseX ,mouseY );
			Piece piece = model.getPiece(point);
			if (piece == null || piece.color != model.playerColor) {
				return;
			}
			model.setPiece(piece , piece.color , Direction.NONE, Weapon.FLAG , piece.revealed);
			model.setGameState(Model.GAME_STATE_PLACING_TRAP);
			this.model.refreshObservers();
		}
		else if (model.getGameState() == Model.GAME_STATE_PLACING_TRAP && model.isPlayerViewer() == false) {

			mouseX = e.getX();
			mouseY = e.getY();
			Point point = board.getBoardCoords(mouseX ,mouseY );
			Piece piece = model.getPiece(point);
			if (piece == null || piece.color != model.playerColor || piece.weapon == Weapon.FLAG) {
				return;
			}
			model.setPiece(piece , piece.color , Direction.NONE, Weapon.TRAP , piece.revealed);
			model.setGameState(Model.GAME_STATE_BUILD_ARMY);
			board.setCursor(Cursor.getDefaultCursor());
			
			// If a user disconnects at the flag or trap placing state and returns to here, the tableFrame will be null, so set it
			tableFrame = conn.getTableFrame();
			tableFrame.setShuffleButton(true);
			this.model.refreshObservers();
		}
		else if (model.getGameState() == Model.GAME_STATE_PLAYING && model.isPlayerViewer() == false) {
	
			currentlyPressedPoint = board.getBoardCoords(mouseX ,mouseY );
			if (board.isPointOnBoard(currentlyPressedPoint) == false) {
				return;
			}
			if (conn.getTable().getCurrentPlayer() != model.playerColor) {
				return;
			}
			
			Piece piece = model.getPiece(currentlyPressedPoint);
			
			// Check if this is the first click, meaning choosing a piece in the clicked cell
			if  (board.isPointOnBoard(board.getPressedPoint()) == false) {

				if (piece == null || piece.color != model.playerColor ) {
					return;
				}
				if ((piece.weapon == Weapon.TRAP) || (piece.weapon == Weapon.FLAG)) {
					return;
				}
				board.setPressedPoint(currentlyPressedPoint);
			}
			else  {
				// It's the second click, meaning - moving the selected piece to the clicked cell
				if (currentlyPressedPoint.equals(board.getPressedPoint())) {
					board.setPressedPointOffBoard();
				}
				else if (piece.color == model.playerColor) {
					if ((piece.weapon == Weapon.TRAP) || (piece.weapon == Weapon.FLAG)) {
						return;
					}
					
					board.setPressedPoint(currentlyPressedPoint);
				}
				else {
					if (!model.checkValidMove(board.getPressedPoint(), currentlyPressedPoint)) {
						return;
					}
					from = board.getPressedPoint();
					to = currentlyPressedPoint;
					conn.send(new CommandMove(new Move(model.getPiece(from).color, from,to)));

					conn.getTableFrame().freezeCountDown();
					
					moveProcedure(from , to);
				}
			}
		}
	}

	/**
	 * Moves the piece from its cell to the selected target cell.
	 * @param from - The original cell of the piece, prior to the move.
	 * @param to   - The ending position of the piece, after the move.
	 */
	public void moveProcedure(final Point from, final Point to) {
		try {
			if (from.equals(Board.createOutOfBoardPoint()) == false)
			{
				final Piece pieceFrom = model.getPiece(from);
				final Piece pieceTo   = model.getPiece(to);
				final Weapon weaponFrom = pieceFrom.weapon;
				final Weapon weaponTo   = pieceTo.weapon;
				
				// If a user joins an on-going game and reaches here, the tableFrame will be null, so get it
				tableFrame = conn.getTableFrame();
				
				final MovePieceAnimation moveAnimation = tableFrame.getMovePieceAnimation(); 
				moveAnimation.setValues(board, from, to, tableFrame);
				model.animation = moveAnimation;
				model.animationOn = true;
				if (weaponTo == Weapon.TRAP) {
					model.trapHide = true;
				}
				
				Thread t = new Thread() {
					public Semaphore s;
					RPSGlassPane glass = tableFrame.getGlass();
					
					public void run() {
						try {
							s = new Semaphore(0);
							moveAnimation.animate(s);
							s.acquire();
							
							if ((weaponTo != Weapon.FLAG) && (weaponTo != Weapon.TRAP) && (weaponTo != Weapon.NONE) ) { 
								glass.showWarAnimation(pieceFrom, pieceTo ,s);
								s.acquire();
								
								glass.setPaneVisible(false);
								glass.revalidate();
								
								// While both weapons are the same, show the short tie animation and let the user choose weapons again
								if (weaponTo == weaponFrom)
								{
									doTieUntilWin(weaponTo);
								}
							}
						}
						catch (Exception e) {
							e.printStackTrace();
						}
						
						model.makeMove(from, to);
						tableFrame.addToHistory(from, to);
						
						tableFrame.switchTurn();
						model.animationOn = false;
						tableFrame.restartCountDown();
						board.setPressedPointOffBoard();
						conn.getTable().moveTurn();
						
						// If we moved to the flag, the user won
						if (model.getPiece(to).weapon == Weapon.FLAG) {
							model.setGameState(Model.GAME_STATE_NOT_PLAYING);
							
							// Stop the countdown
							tableFrame.freezeCountDown();
							
							String message = "The game is over !" + Settings.NEW_LINE;
							
							// If the user is a player, inform him that the game has ended and what is the result - whether he won or lost
							if (model.isPlayerViewer() == false)
							{
								CommandGameResultToOwner commandGameResultToOwner = null;
								message += "You ";
								
								// Check if the user won
								if (pieceTo.color != model.playerColor) {
									message += "won ! Congratulations !";
									commandGameResultToOwner = new CommandGameResultToOwner(model.playerColor.toString());		
								}
								else {
									message += "lost. That's ok, we still love you.";
									commandGameResultToOwner = new CommandGameResultToOwner(PlayerColor.flipColor(model.playerColor).toString());
								}
								conn.send(commandGameResultToOwner);
							}
							// Else, the player is a viewer. Inform him that the game ended and which player won
							else
							{
								message += "The ";
								
								// Check if the blue user won
								if (pieceTo.color != model.playerColor) {
									message += model.playerColor.toString() + " ";
								}
								else {
									message += PlayerColor.flipColor(model.playerColor).toString() + " ";
								}
								
								message += " Player won the game !";
							}
							
							tableFrame.endGame();
							
							JOptionPane.showMessageDialog(tableFrame , message, "The Game Has Ended" , JOptionPane.INFORMATION_MESSAGE);
						}
						
						model.refreshObservers();
					}
					
					private void doTieUntilWin(Weapon initialWeapon) throws InterruptedException
					{
						Weapon toWeapon = initialWeapon;
						Weapon fromWeapon = initialWeapon;
						while (toWeapon == fromWeapon) {
							
							if (model.isPlayerViewer() == false)
							{
								// Let the user choose another weapons
								glass.showWarChoosePieceComponent(s);
								s.acquire();
								
								// Send to the other players the weapon that the user selected
								Weapon weaponChosen = (Weapon) glass.getReturnedFromPanel();
								CommandWeaponSelection commandWeaponSelection = new CommandWeaponSelection(conn.getUsername() , weaponChosen);
								conn.send(commandWeaponSelection);
								
								model.selectedWeapon = weaponChosen;
								
								glass.setPaneVisible(false);
								
								// If the other player hasn't selected a weapon yet, let the user know we're waiting for him
								if (model.enemyWeaponSelect == Weapon.NONE) {
									glass.showMessagePanel("Waiting For Other Player...");
									model.WarDrawSemaphore.acquire();
									
									glass.setPaneVisible(false);
								}
							}
							else
							{
								glass.showMessagePanel("Players are re-arming...");
								model.WarDrawSemaphore.acquire();
								
								glass.setPaneVisible(false);
							}
							
							// Set the new weapons to the pieces
							boolean iAmAttacker = (model.playerColor == pieceFrom.color);   
							toWeapon =(iAmAttacker) ?  model.enemyWeaponSelect : model.selectedWeapon;
							fromWeapon = (iAmAttacker) ? model.selectedWeapon : model.enemyWeaponSelect ; 
							model.getPiece(from).weapon = fromWeapon;
							model.getPiece(to).weapon = toWeapon;
							
							glass.showWarAnimation(pieceFrom, pieceTo ,s);
							s.acquire();
							
							glass.setPaneVisible(false);
							model.selectedWeapon =  Weapon.NONE;
							model.enemyWeaponSelect = Weapon.NONE;
						}
					}
				};
				t.start();
			}
			else
			{
				// If a user joins an on-going game and reaches here, the tableFrame will be null, so get it
				tableFrame = conn.getTableFrame();
				
				tableFrame.switchTurn();
				model.animationOn = false;
				tableFrame.restartCountDown();
				board.setPressedPointOffBoard();
				conn.getTable().moveTurn();
			}
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		
		this.model.refreshObservers();
	}
	
	public void start() {
		tableFrame = conn.getTableFrame();
		
		model.start();
		
		model.setGameState(Model.GAME_STATE_PLACING_FLAG);
			
		if (model.isPlayerViewer() == false)
		{
			Cursor invisibleCursor = Toolkit.getDefaultToolkit().createCustomCursor(Toolkit.getDefaultToolkit().getImage(""),
																					new Point(0,0),
																					"invisible");
			board.setCursor(invisibleCursor);
		}
	}
}
	